/*
 * SPDX-FileCopyrightText: 2022-2024 Andrew Gunnerson
 * SPDX-License-Identifier: GPL-3.0-only
 */

package com.sanji.audio_test.format

import android.media.AudioFormat
import android.media.MediaFormat
import android.util.Log
import com.sanji.audio_test.Preferences
import com.sanji.audio_test.extension.frameSizeInBytesCompat
import java.io.FileDescriptor

/**
 * 音频格式抽象基类
 * 
 * 定义了音频格式的基本属性和方法，所有具体的音频格式实现都继承此类
 */
sealed class Format {
    /** 格式面向用户的名称 */
    abstract val name: String

    /** 存储编码音频流的容器的 MIME 类型 */
    abstract val mimeTypeContainer: String

    /**
     * 容器内编码音频流的 MIME 类型
     *
     * 对于某些格式，可能与 [mimeTypeContainer] 相同
     */
    abstract val mimeTypeAudio: String

    /** 格式是否直接传递 PCM 样本而不进行编码 */
    abstract val passthrough: Boolean

    /** 格式参数范围和默认值的详细信息 */
    abstract val paramInfo: FormatParamInfo

    /** 支持的采样率的默认信息 */
    abstract val sampleRateInfo: SampleRateInfo

    /** 仅包含 [MediaFormat.KEY_MIME] 的基础 [MediaFormat] */
    protected val baseMediaFormat: MediaFormat
        get() = MediaFormat().apply {
            setString(MediaFormat.KEY_MIME, mimeTypeAudio)
        }

    /**
     * 创建一个表示编码音频的 [MediaFormat]，其参数与指定的输入 PCM 音频格式匹配
     *
     * @param audioFormat [AudioFormat.getSampleRate] 不能为
     * [AudioFormat.SAMPLE_RATE_UNSPECIFIED]
     * @param param 格式特定的参数值。必须根据 [paramInfo] 有效
     *
     * @throws IllegalArgumentException 如果 [FormatParamInfo.validate] 失败
     */
    fun getMediaFormat(audioFormat: AudioFormat, param: UInt?): MediaFormat {
        if (param != null) {
            paramInfo.validate(param)
        }

        val format = baseMediaFormat.apply {
            setInteger(MediaFormat.KEY_CHANNEL_COUNT, audioFormat.channelCount)
            setInteger(MediaFormat.KEY_SAMPLE_RATE, audioFormat.sampleRate)
            setInteger(KEY_X_FRAME_SIZE_IN_BYTES, audioFormat.frameSizeInBytesCompat)
        }

        updateMediaFormat(format, param ?: paramInfo.default)

        return format
    }

    /**
     * 使用与格式特定参数相关的参数键更新 [mediaFormat]
     *
     * @param param 保证根据 [paramInfo] 有效
     */
    protected abstract fun updateMediaFormat(mediaFormat: MediaFormat, param: UInt)

    /**
     * 创建一个产生 [mediaFormat] 输出的 [Encoder]
     *
     * @param mediaFormat 由 [getMediaFormat] 返回的 [MediaFormat] 实例
     * @param container 由 [getContainer] 返回的 [Container] 实例
     *
     * @throws Exception 如果设备不支持使用 [mediaFormat] 中设置的参数进行编码，
     * 或者配置编码器失败
     */
    fun getEncoder(mediaFormat: MediaFormat, container: Container): Encoder =
        if (passthrough) {
            PassthroughEncoder(mediaFormat, container)
        } else {
            MediaCodecEncoder(mediaFormat, container)
        }

    /**
     * 创建一个容器复用器，接收编码输入并将复用后的输出写入 [fd]
     *
     * @param fd 容器不拥有文件描述符的所有权
     */
    abstract fun getContainer(fd: FileDescriptor): Container

    companion object {
        private val TAG = Format::class.java.simpleName

        /** 自定义键：每帧的字节数 */
        const val KEY_X_FRAME_SIZE_IN_BYTES = "x-frame-size-in-bytes"

        /** 所有支持的格式列表 */
        val all: List<Format> by lazy {
            val formats = mutableListOf<Format>()

            for (constructor in arrayOf(
                ::OpusFormat,
                ::AacFormat,
                ::FlacFormat,
                { WaveFormat },
                ::AmrWbFormat,
                ::AmrNbFormat,
            )) {
                try {
                    formats.add(constructor())
                } catch (e: IllegalArgumentException) {
                    Log.w(TAG, "使用 $constructor 初始化失败", e)
                }
            }

            formats
        }
        
        /** 默认格式 */
        private val default: Format = all.first()

        /** 根据名称查找输出格式 */
        fun getByName(name: String): Format? = all.find { it.name == name }

        /**
         * 从首选项中获取保存的格式，或回退到默认格式
         *
         * 如果设置了参数，它会被限制在格式允许的参数范围内。
         * 同样，如果设置了采样率，它会被设置为最接近的有效值。
         */
        fun fromPreferences(prefs: Preferences): Triple<Format, UInt?, UInt?> {
            // 如果保存的格式仍然有效，则使用它
            val format = prefs.format ?: default

            // 将保存的值转换为最接近的有效值（例如，如果在新版本中更改了比特率范围）
            val param = prefs.getFormatParam(format)?.let {
                format.paramInfo.toNearest(it)
            }

            // 采样率也做同样的处理
            val sampleRate = prefs.getFormatSampleRate(format)?.let {
                format.sampleRateInfo.toNearest(it)
            }

            return Triple(format, param, sampleRate)
        }
    }
}